/*
Copyright 2007 Raymond Giorgi

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */

package edu.pitt.CS2510;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;

import edu.pitt.CS2510.Grids.Grid;
import edu.pitt.CS2510.Grids.GridPiece;
import edu.pitt.CS2510.Messages.Message;
import edu.pitt.CS2510.Nodes.CentralNode;
import edu.pitt.CS2510.Nodes.Node;
import edu.pitt.CS2510.Nodes.WorkerNode;
import edu.pitt.CS2510.View.Connection;
import edu.pitt.CS2510.View.EnemyBlock;
import edu.pitt.CS2510.View.FriendlyBlock;
import edu.pitt.CS2510.View.View;

/**
 * @author Alichino
 * 
 *         TODO To change the template for this generated type comment go to
 *         Window - Preferences - Java - Code Style - Code Templates
 */
public class Simulator {

	private final Grid grid;
	private final int enemyUnits = 10;
	private final int height = 50;
	private final int width = 50;
	ArrayList<Node> nodes = new ArrayList<Node>();
	ArrayList<Node> currentNodes = new ArrayList<Node>();
	ArrayList<Message> messages = new ArrayList<Message>();
	View view = null;
	private final int nodeNo = 101;

	@SuppressWarnings("unchecked")
	public Simulator() {
		int cycle = 1;
		view = new View(height, width);
		grid = new Grid(height, width);
		System.out.println("Grid Initialized");

		grid.populateWithEnemies(enemyUnits);
		System.out.println("Enemies Populated");

		nodes.add(new CentralNode(this, grid.getEnemies(), grid));
		for (int i = 1; i < nodeNo; i++) {
			final WorkerNode node = new WorkerNode(this, i, grid.clone());
			nodes.add(node);
			((CentralNode) nodes.get(0)).addIdleNode(node.getId());
		}
		System.out.println("Nodes Initialized");

		((CentralNode) nodes.get(0)).setEnemies(grid.getEnemies());
		// View controller = new View(height, width);
		System.out.println("Starting Simulation");

		// The simulation ends once all enemies have been defeated
		while (grid.enemiesRemain()) {

			// This sets the location of every worker node in the grid. Without
			// this
			// the number of enemy nodes does not get updated
			for (final Node node : nodes) {
				if (node instanceof WorkerNode) {
					final Coordinate co = ((WorkerNode) node).getCoordinate();
					if (co != null) {
						final GridPiece piece = grid.getBlock(co.getX(), co
								.getY());
						if (!piece.isUsed()) {
							piece.setUsed(true);
							piece.setTeam(1);
						}
					}
				}
			}

			// Update information related to enemies (number, location, active,
			// etc)
			final ArrayList temp = new ArrayList();
			for (final Iterator<GridPiece> iter = grid.getEnemies().iterator(); iter
					.hasNext();) {
				final GridPiece piece = iter.next();
				// Get the neighbors of the current enemy
				final ArrayList list = grid.getNeighbors(piece.getX(), piece
						.getY());
				int no = 0;
				// count how many nodes surround the enemy
				for (final Iterator neighbors = list.iterator(); neighbors
						.hasNext();) {
					final GridPiece neighbor = (GridPiece) neighbors.next();
					if (neighbor.isUsed()) {
						no++;
					}
				}
				// Decrement the number of units of this enemy by the number of
				// nodes
				// surrounding it. In other words, if an enemy has 3 nodes
				// around it, it will
				// decrement its unit count by 3.
				piece.setUnits(piece.getUnits() - no);
				if (piece.getUnits() <= 0) {
					System.out.println("Node Removed- X:" + piece.getX()
							+ " Y:" + piece.getY() + " C:" + cycle + "\n");
					iter.remove();
				} else {
					temp.add(piece);
				}
			}
			// update the list of enemies
			grid.setEnemies(temp);

			// tell each node to perform its respective actions
			for (final Node node : nodes) {
				node.performAction();
			}

			// This is the code that updates the view.
			// This adds a shape for every enemy block
			for (final GridPiece piece : grid.getEnemies()) {
				view.addShape(new EnemyBlock(piece.getX(), piece.getY(), piece
						.getUnits()));
			}
			// Get the location of the CentralNode (should be off the grid)
			final Coordinate cbCo = view.getCentralBlockLocation();
			// This loop goes through every supervisor node
			for (final Integer id : ((CentralNode) nodes.get(0))
					.getCoordinators()) {
				// Get the coordinate of the Supervisor and add the shape to the
				// view
				final Coordinate co = ((WorkerNode) nodes.get(id))
						.getCoordinate();
				view.addShape(new FriendlyBlock(Color.green, co.getX(), co
						.getY()));
				view.addShape(new Connection(cbCo.getX(), cbCo.getY(), co
						.getX(), co.getY()));
				// This loop goes through every Grunt the supervisor has control
				// over
				for (final Integer helper : ((WorkerNode) nodes.get(id))
						.getHelpers()) {
					// Get the coordinate of the Grunt and add it
					final Coordinate helperCo = ((WorkerNode) nodes.get(helper))
							.getCoordinate();
					view.addShape(new FriendlyBlock(Color.yellow, helperCo
							.getX(), helperCo.getY()));
					view.addShape(new Connection(co.getX(), co.getY(), helperCo
							.getX(), helperCo.getY()));
				}
			}
			view.update();

			// A quick hack to make sure the simulator goes through one cycle
			// every second
			final long time = System.currentTimeMillis() + 3000;
			while (System.currentTimeMillis() < time) {
				;
			}
			// Increment the cycle count. Useful for debugging
			cycle++;
			System.out.println(cycle);
		}
	}

	/**
	 * The entry point of the program
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		new Simulator();
	}

	/**
	 * Add 'm' to the reciever's message queue
	 * 
	 * @param m
	 */
	public void sendMessage(Message m) {
		nodes.get(m.getTo()).recieveMessage(m);
	}

	/**
	 * The one place where the simulator 'cheats.' This function takes a
	 * coordinate as an argument and supplies the node with the unit count of
	 * the enemy there.
	 * 
	 * @param co
	 * @return the number of enemies at 'co'
	 */
	public int getEnemiesAtCo(Coordinate co) {
		return grid.getUnitsAtBlock(co.getX(), co.getY());
	}
}
